package firbi.base;

import java.util.ArrayList;
import java.util.HashMap;

import android.app.Dialog;
import android.app.ListActivity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.ContextMenu;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.TabHost;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.TabHost.TabSpec;

import com.google.android.maps.GeoPoint;

import firbi.base.com.BusRoute;
import firbi.base.com.BusStop;
import firbi.base.util.Constants;
import firbi.base.util.FirbiHelper;

/**
 * RouteView takes in a bus route id and displays the stops in that route, sorted
 * by distance from either the current location or the passed in location. It also
 * first calls DestinationView if the destination is not also passed in.
 * 
 * The activity requires that an int be passed in the intent under the key "bus_route_id".
 * This key should correspond an id for a BusRoute.
 * @author Kathryn Winglee
 *
 */

public class RouteView extends ListActivity implements LocationListener {
	private static final int sGET_DEST = 0;//activity to get the destination
	private static boolean progressOn = true;//whether to show the progress display
	private static boolean threadDone;//whether the thread to firbi com is done
	
	//constants for gps to determine if the result is valid
	private static final int sMIN_GPS_ACC = 25;
	private static final int sMIN_GPS_TIME = 180000;  //3 mins
	
	private static final String sERROR = "Unable to find route.";
	
	//private fields for the activity
	private String mDestination;//destination user selected
	private BusRoute mRoute;//BusRoute for the view
	private ArrayList<HashMap<String,String>> mList = new ArrayList<HashMap<String,String>>();//list displayed in ListView
	private GeoPoint mLoc;//location
	private BusStop[] mStops;//stops corresponding to mRoute
	private ImageButton mGPSButton;//gps button
	private boolean mFromDest;//whether to go back to DestinationView
	private boolean mValidLocation;//whether a gps location is valid
	private LocationManager mLocManager;//for getting gps locations
	private ProgressDialog mProgressD;//displays progress while communication with firbi is running
	private Dialog mHelpD;//help dialog
	
	//favorites
	private FavsDbAdapter mFavDbHelper;	//database adapter
	private static final int sADD_FAV = 0; 
	private static final int sCANCEL= sADD_FAV+1;
	
	/* Methods for the life cycle of the Activity*/
	/**
	 * Called when RouteView is first created.
	 * The bundle must have ROUTE_KEY in its extras.
	 * 
	 * @param savedInstanceState	data used if the activity is being re-initialized
	 */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mLocManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
        //setContentView(R.layout.routeview);
        Bundle extras = getIntent().getExtras(); 
        if (extras != null && extras.containsKey(Constants.sROUTE_KEY)) {
        	String id = extras.getString(Constants.sROUTE_KEY);
        	mRoute = BusRoute.find(id);
        	Log.i("route", id);
        	if(extras.containsKey(Constants.sDEST_KEY)) {
            	mDestination = extras.getString(Constants.sDEST_KEY);       
            	Log.i("route", mDestination);
        	} else {
        		mDestination = null;
        	}
        	if(extras.containsKey(Constants.sROUTE_LAT_KEY) && extras.containsKey(Constants.sROUTE_LON_KEY)) {
        		int lat = extras.getInt(Constants.sROUTE_LAT_KEY);
        		int lon = extras.getInt(Constants.sROUTE_LON_KEY);
        		mLoc = new GeoPoint(lat, lon);
        		setContentView(R.layout.routeview);
        		mValidLocation = true;
        	} else {
        		mLoc = null;
        		setContentView(R.layout.routeview_no_gps);
        		mValidLocation = false;
        	}
        } else {
        	displayMessage(sERROR);
            setResult(RESULT_CANCELED);
            finish();
        }
        if(mRoute == null) {
        	displayMessage(sERROR);
        } else {
        	//set up the view
        	mLocManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
        	mLocManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 6000, 10, this);
        	registerForContextMenu(getListView());
        	TextView routeLabel = (TextView) findViewById(R.id.route);
        	TextView destLabel = (TextView) findViewById(R.id.destination);
        	routeLabel.setText(mRoute.getRouteNumber());
        	destLabel.setText(mDestination);
        	mGPSButton = (ImageButton)findViewById(R.id.update_gps_button);
        	gpsListener();
        	mFromDest = false;
        	setUpHelp();
        	//favorites
        	mFavDbHelper = new FavsDbAdapter(this);
        	mFavDbHelper.open();

        	//call DestinationView to get the destination
        	if(mDestination == null) {
        		Intent intent = new Intent(this, DestinationView.class);
        		intent.putExtra(Constants.sROUTE_KEY, mRoute.getId());
        		startActivityForResult(intent, sGET_DEST);
        	} else {
        		Log.i("route", "route_id: "+mRoute.getId());
        		Log.i("route", "destination: "+mDestination);
        		fillData(true);
        	}
        }
    }
    
    /**
	 * Called on return from from an activity called for result.
	 * On return from DestinationView, gets the destination and fills the view with stops.
	 * 
	 * @param requestCode	code of the activity returning
	 * @param resultCode	the result of the activity returning
	 * @param intent		intent passed back from the returning activity, containing information from that activity
	 */
	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
		super.onActivityResult(requestCode, resultCode, intent);
		mFromDest = true;
		Bundle extras = intent.getExtras();
		switch(requestCode) {
		case sGET_DEST:
			if(resultCode == RESULT_OK) {
				if(extras != null) {
					mDestination = extras.getString(Constants.sDEST_KEY);
	        		Log.i("route", "route_id: "+mRoute.getId());
	        		Log.i("route", "destination: "+mDestination);
				} else {
					 displayMessage(sERROR);
				}
				fillData(true);
			} else if(resultCode == Constants.sRESULT_BACK) {
				Log.i("route", "onActivityResult back pushed");
				mFromDest = false;
				onKeyDown(KeyEvent.KEYCODE_BACK, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK));
			} else if(resultCode == RESULT_CANCELED){
				mDestination = "";
			} else {
				displayMessage(sERROR);
			}
		    break;
		}
	    TextView destLabel = (TextView) findViewById(R.id.destination);
	    destLabel.setText(mDestination);
	}

	/**
     * Called when the Activity is paused
     */
    @Override 
	public void onPause() {
		mLocManager.removeUpdates(this);
		super.onPause();
	}

    /**
     * Called when the Activity is stopped
     */
	@Override
	public void onStop() {
		mLocManager.removeUpdates(this);
		super.onStop();
	}

	/**
	 * Called when the Activity restarts from onPause
	 */
	@Override
	public void onResume() {
		mLocManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 6000, 10, this);
		super.onResume();
	}

	/**
	 * Final cleanup before activity is destroyed.
	 */
	@Override
	public void onDestroy() {
		mLocManager.removeUpdates(this);
		super.onDestroy();
	}

	/**
	 * Used to save the state so the activity can be restored to the current state if
	 * another activity comes back to it before it is killed
	 * 
	 * @param savedInstanceState	Bundle that stores the saved state
	 */
	@Override
	public void onSaveInstanceState(Bundle savedInstanceState) {
		savedInstanceState.putString(Constants.sROUTE_KEY, mRoute.getId());
		savedInstanceState.putString(Constants.sDEST_KEY, mDestination);
		if(mLoc != null) {
			savedInstanceState.putInt(Constants.sROUTE_LAT_KEY, mLoc.getLatitudeE6());
			savedInstanceState.putInt(Constants.sROUTE_LON_KEY, mLoc.getLongitudeE6());
		}
		super.onSaveInstanceState(savedInstanceState);    	
	}

	/* Methods for pushing buttons (menu, context menu, back, etc)*/
	/**
	 * Initializes the menu
	 * 
	 * @param menu		the menu to put items in
	 * 
	 * @return true if the menu is to be displayed; false to not show the menu
	 */
	public boolean onCreateOptionsMenu(Menu menu) {
		MenuInflater inflater = getMenuInflater();
	    inflater.inflate(R.menu.routeview_menu, menu);
	    return true;
	}

	/**
	 * Called when one of the items in the menu is clicked on
	 * 
	 * @param item		menu item clicked on
	 * 
	 * @return 	false if the normal menu processing should continue 
	 */
	public boolean onOptionsItemSelected(MenuItem item) {
		Intent i;
	    switch (item.getItemId()) {
			case R.id.menu_item_mapsearch://go to the map view of search
				i = new Intent(this, Search.class);
				i.putExtra("isMapView", true);
				startActivity(i);
				return true;
			case R.id.menu_item_listsearch://go to the list view of search
				i = new Intent(this, Search.class);
				i.putExtra("isMapView", false);
				startActivity(i);
				return true;
			case R.id.menu_item_favs://go to the favorites view
				i = new Intent(this, Favorites.class);
				startActivity(i);
				return true;
			case R.id.menu_item_help://open the help box
				mHelpD.show();
				return true;
		}
	    return false;
	}

	/**
	 * Called when one of the keys is pushed.
	 * Back button is overwritten to call destination view if destination was not initially
	 * passed in, or to perform back as normal otherwise.
	 * 
	 * @param keyCode	the key pushed
	 * @param ev		description of the key event
	 * @return false if event not handled, true otherwise
	 */
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent ev) {
		switch(keyCode) {
		case KeyEvent.KEYCODE_BACK:
			if(mFromDest) {
				mFromDest = false;
	        	Intent intent = new Intent(this, DestinationView.class);
	        	intent.putExtra(Constants.sROUTE_KEY, mRoute.getId());
	        	startActivityForResult(intent, sGET_DEST);
	        	return true;
			} else {
				finish();
				return true;
			}
		}
		return super.onKeyDown(keyCode, ev);
	}

	/**
	 * Called when the user clicks on one of the stops in the list.
	 * Once calls, starts StopView, passing the id for the stop that was clicked on.
	 * 
	 * @param l			the ListView clicked on 
	 * @param v			the view clicked on within the list
	 * @param position	position of v in the list
	 * @param id		row id of the item clicked
	 */
	@Override
	protected void onListItemClick(ListView l, View v, int position, long id) {
	    super.onListItemClick(l, v, position, id);
	    Intent intent = new Intent(this, StopView.class);
	    intent.putExtra(Constants.sROUTE_KEY, mRoute.getId());
	    intent.putExtra(Constants.sSTOP_KEY, mStops[position].getId());
	    Log.v("route", mStops[position].getAddress());
		startActivity(intent);
	}

	/**
	 * Called when a context menu for the view is about to be shown. In this case,
	 * the context menu is for adding to favorites.
	 * 
	 * @param menu		the context menu being built
	 * @param v			view the context menu is being built for
	 * @param menuInfo	extra information about the context menu being built
	 */
	@Override
	public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
		super.onCreateContextMenu(menu, v, menuInfo);
	    menu.add(0, sADD_FAV, 0, "Add to favorites");
	    menu.add(0, sCANCEL, 0, "Cancel");
	}

	/**
	 * Called when an item in the context menu (in this case add to 
	 * favorites) is  clicked on, adding the clicked on stop and route
	 * to favorites or exited the menu, depending on user input
	 * 
	 * @param item		the selected context menu item
	 */
	@Override
	public boolean onContextItemSelected(MenuItem item) {
		switch(item.getItemId()) {
			case sADD_FAV:
				AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
				BusStop stop = mStops[info.position];
				long favResult = mFavDbHelper.createFav(mDestination, mRoute.getRouteNumber(), mRoute.getId(), stop.getId());
				if(favResult == -1) {
					displayMessage(Constants.sPROBLEMS_WITH_DATABASE);
				} else if(favResult == -2) {
					displayMessage(Constants.sALREADY_FAVORITE);
				} else {
					displayMessage(Constants.sADDED_FAVORITE);
				}
				return true;
			case sCANCEL:
				return true;
		}
		return super.onContextItemSelected(item);
	}

	/* Location listener methods -- must be implemented*/
	/**
	 * Called when the location has changed. The location is valid.
	 * 
	 * @param arg0	the new location
	 */
	public void onLocationChanged(Location arg0) {
		mValidLocation = true;
	}

	/**
	 * Called when the provider is disabled. Any locations are now invalid.
	 * 
	 * @param provider	the name of the location provider associated with this change.
	 */
	public void onProviderDisabled(String provider) {
		mValidLocation = false;
	}

	/**
	 * Called when the provider is enabled. Any locations are now valid.
	 * 
	 * @param provider	the name of the location provider associated with this change.
	 */
	public void onProviderEnabled(String provider) {
		mValidLocation = true;
	}

	public void onStatusChanged(String provider, int status, Bundle extras) {	
	}

	/* Methods to display the list info*/
	/**
	 * Starts filling in the list by creating a thread for 
	 * getting the stops.
	 */
    private void fillData(final boolean dialog) {
    	if(progressOn && dialog) {
    		mProgressD = ProgressDialog.show(this, 
    				"", "Loading. Please wait...", true, false);
    	}
    	Thread com = new Thread() {
    		public void run() {
    			threadDone = false;
    			//get stops
    			if(mDestination == null) {
    	    		mStops = mRoute.getAllStops();
    	    		Log.v("route", "getAllStops complete");
    	    	} else {
    	    		Log.v("route", mDestination);
    	    		mStops = mRoute.getStops(mDestination.split(", "));
    	    		Log.v("route", "getStops complete");
    	    	}
    			//mStops = BusStop.find(new GeoPoint(47668579, -122288293), 300);//delete when done
    	    	Message msg = new Message();
    	    	Bundle b = new Bundle();
    	    	b.putBoolean("dialog", dialog);
    	    	msg.setData(b);
    	    	dataHandler.sendMessage(msg);
    		}
    	};
    	com.start();
    }

	/**
     * Handles the completion of the thread in fillData by filling the view with the stop addresses and 
     * the distance from the current position if gps is displayed.
     */
    private Handler dataHandler = new Handler() {
    	@Override
    	public void handleMessage(Message msg) {
    		Log.i("route", "handler called");
    		Bundle b = msg.getData();
    		//add stops to view
	    	if(mStops != null) {
	    		double[] dist = new double[mStops.length];
	    		for(int i = 0; i < dist.length; i++) {
	    			dist[i] = -1;
	    		}
	    		if(mLoc != null) {
	    			for(int i = 0; i < mStops.length;i++) {
	    				dist[i] = FirbiHelper.getDistance(mStops[i].getLocation(), mLoc, false)/5280;
	    			}
	    			sort(dist);
		    		for(int i = 0; i < mStops.length; i++) {
		    			add(mStops[i].getAddress(), dist[i]);
		    		}
		    		refreshListGPS();
	    		} else {
	    			Log.i("route", "mLoc is null");
		    		for(int i = 0; i < mStops.length; i++) {
		    			add(mStops[i].getAddress());
		    		}
		    		refreshListNoGPS();
	    		}
	    	}
    		if(progressOn && b.getBoolean("dialog")) {
	    		mProgressD.dismiss();
    		}
    		threadDone = true;
    	}
    };
    
    /**
     * Adds a stop (a row) to the view. Used when there is GPS. To be seen on the list,
     * refreshList must be called.
     * 
     * @param address 	the address of the stop
     * @param dist		the distance of the stop from mLoc
     */
	protected void add(String address, double dist) {
		HashMap<String,String> d = new HashMap<String,String>();
		if(dist < 0) {
			d.put("stopCol", address);
			d.put("distanceCol", "NA");
		} else {
			d.put("stopCol", address);
			d.put("distanceCol", Double.toString(round(dist)));
		}
		mList.add(d);
	}
	
	/**
     * Adds a stop (a row) to the view. Used when there is no GPS. To be seen in the
     * list refreshList must be called.
     * 
     * @param address 	the address of the stop
     */
	protected void add(String address) {
		HashMap<String,String> d = new HashMap<String,String>();
		d.put("stopOnly", address);
		mList.add(d);
	}
	
	/**
	 * Refreshes the list, adding any new elements that have been added to the list.
	 * Called if there is gps.
	 */
	private void refreshListGPS() {
		SimpleAdapter dlist = new SimpleAdapter( 
				this, 
				mList,
				R.layout.routeview_list,
				new String[] {"stopCol", "distanceCol"},
				new int[] {R.id.stopCol, R.id.distanceCol});
		setListAdapter(dlist);	
	}
	
	/**
	 * Refreshes the list, adding any new elements that have been added to the list.
	 * Called if there is no GPS.
	 */
	private void refreshListNoGPS() {
		SimpleAdapter dlist = new SimpleAdapter( 
				this, 
				mList,
				R.layout.routeview_list_no_gps,
				new String[] {"stopOnly"},
				new int[] {R.id.stopOnly});
		setListAdapter(dlist);
	}

	/**
	 * rounds the given number to 2 decimal places
	 * @param d		the number to round
	 * @return		d rounded to 2 decimal places
	 */
	private double round(double d) {
		d=Math.round(d*100);
		return d/100.0;
	}
    
	/**
     * The controls for the GPS button. When the button is pushed, updates the location to
     * the current position and if necessary re-sorts the list by distance. 
     */
    private void gpsListener() {
    	mGPSButton.setOnClickListener(new ImageButton.OnClickListener() {
            public void onClick(View v) {
            	Location loc = mLocManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
            	boolean validLoc = validLocation(loc, System.currentTimeMillis());
            	boolean sameLoc = validLoc && sameLocation(loc, mLoc);
            	if (validLoc && !sameLoc) {      /* update location and list */
            		Log.v("route", "update mLoc");
            		if(mLoc == null) {
            			Log.v("route", "set view");
            			setContentView(R.layout.routeview);
            	        TextView routeLabel = (TextView) findViewById(R.id.route);
            	        TextView destLabel = (TextView) findViewById(R.id.destination);
            	        routeLabel.setText(mRoute.getRouteNumber());
            	        destLabel.setText(mDestination);
            		}
            		mLoc = new GeoPoint((int)(loc.getLatitude()*1E6), (int)(loc.getLongitude()*1E6));
            		sameLoc = false;
            	} else if(!validLoc) {
            		displayMessage("No GPS signal.");
            	}     	
            	//do nothing if same location
            	if(!sameLoc && mLoc != null && mStops != null) {
            		double dist[] = new double[mStops.length];
            		for(int i = 0; i < mStops.length; i++) {
            			dist[i] = FirbiHelper.getDistance(mStops[i].getLocation(), mLoc, false);
            		}
            		sort(dist);
            		mList.clear();
                	fillData(false);
            	}
            }
        });
    }
    
    /**
     * Sorts dist and mStops in increasing order in dist, changing the data in
     * dist and mStops. The sorting algorithm is merge sort. The array itself
     * will contain the sorted result when sort returns.
     * 
     * @param a		array of distances corresponding to the current mStops array
     */
    protected void sort(double[] dist) {
    	if(dist == null || dist.length <= 1) {
    		return;
    	}
    	int n = dist.length;
		int k = 0;
		double[] tmpa = new double[n];
		BusStop[] tmps = new BusStop[n];
		int i=2;
		for(; i < n; i *=2) {
			if(k%2 == 0) {
				for(int j = 0; j < n; j += i) {
					merge(dist,tmpa, mStops, tmps,j,j+i/2,j+i-1,n);
				}
			} else {
				for(int j = 0; j < n; j+= i) {
					merge(tmpa,dist,tmps, mStops, j,j+i/2,j+i-1,n);
				}
			}
			k++;
		}
		if(k%2 == 1){
			merge(tmpa,dist,tmps, mStops, 0,i/2,i,n);
		} else {
			merge(dist,tmpa,mStops,tmps,0,i/2,i,n);
			for(int j = 0; j < n; j++) {
				dist[j] = tmpa[j];
				mStops[j]=tmps[j];
			}
		}
    }
    
    /**
     * Helper method for sort that merges parts of arrays in increasing order (merges the 
     * sub array from leftPos to rightPos with the array from rightPos to rightEnd).
     * temp and temps will have the merged result.
     * 
     * assumes that the sub arrays are already in sorted increasing order.
     * 
     * @param dist		The first array to merge.
     * @param temp		The temporary array to put the results of merging dist.
     * @param stops		The second array to merge.
     * @param temps		The temporary array to put the results of merging stops.
     * @param leftPos	The left position to merge.
     * @param rightPos	The right position to merge.
     * @param rightEnd	The end of the second array being merged.
     * @param n			The size of a (or stops).
     */
    private void merge(double[] dist, double[] temp, BusStop[] stops, BusStop[] temps, int leftPos, int rightPos, int rightEnd, int n) {
    	int leftEnd = rightPos-1;
		int tmpPos = leftPos;
		if(leftEnd >= n) {
			leftEnd = n - 1;
		}
		if(rightEnd >= n) {
			rightEnd = n - 1;
		}
		while(leftPos <= leftEnd && rightPos <= rightEnd){
			if(dist[leftPos] <= dist[rightPos]) {
				temps[tmpPos]=stops[leftPos];
				temp[tmpPos++]=dist[leftPos++];
			}else{
				temps[tmpPos]=stops[rightPos];
				temp[tmpPos++]=dist[rightPos++];
			}
		}
		while(leftPos<=leftEnd) {
			temps[tmpPos]=stops[leftPos];
			temp[tmpPos++] = dist[leftPos++];
		}
		while(rightPos<=rightEnd) {
			temps[tmpPos]=stops[rightPos];
			temp[tmpPos++] = dist[rightPos++];
		}
	}
    
    /**
     * Check whether the location is valid based on time and accuracy of fix
     * @return: True is location is valid, false otherwise
     */
    private boolean validLocation(Location loc, long currentTime) {
    	if (loc != null)
    		Log.v("TIme", currentTime + "\t" + loc.getTime());
    	if (loc==null || loc.getAccuracy() > sMIN_GPS_ACC)
    		return false;
    	if (Math.abs(loc.getTime()-currentTime) <= sMIN_GPS_TIME) { /* Sometimes the time stamps work and sometimes they don't */
    		mValidLocation = true; 
    	}
    	return mValidLocation;            /* mValidLocation is another check in case the time stamp is not working */
    }
    
    /**
     * Checks whether the location is the same
     * 
     * @param loc 	location 
     * @param p 	point to be checked against
     * @return 		true if they are the same
     */
    private boolean sameLocation(Location loc, GeoPoint p) {
    	if (loc==null || p==null)
    		return false;
    	return sameLocation(new GeoPoint((int)(loc.getLatitude()*1E6), (int)(loc.getLongitude()*1E6)), p);
    }

    /**
     * Checks whether the two GeoPoints are the same location
     * 
     * @param p1	the first GeoPoint
     * @param p2	the second GeoPoint
     * @return		true if p1 and p2 are the same location
     */
    private boolean sameLocation (GeoPoint p1, GeoPoint p2) {
    	if (p1==null || p2==null)
    		return false;
    	 float [] distance = new float[1];
 		Location.distanceBetween(p1.getLatitudeE6()/1E6d, p1.getLongitudeE6()/1E6d, 
 		    	p2.getLatitudeE6()/1E6d, p2.getLongitudeE6()/1E6d, distance);
 		return Math.abs(distance[0]) < 20;
    }
    
    /**
     * Sets up the help dialog box.
     */
    private void setUpHelp() {
    	LayoutInflater li = (LayoutInflater)getApplicationContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    	View v = li.inflate(R.layout.routeview_help_dialog, null);
        
        TabHost helpTab = (TabHost)v.findViewById(R.id.tabhost);
        helpTab.setup();
        TabSpec ts = helpTab.newTabSpec("TAB_TAG_1"); 
        ts.setIndicator("Main");
        ts.setContent(R.id.routeview_help_tab_main);
        helpTab.addTab(ts);
        TabSpec ts2 = helpTab.newTabSpec("TAB_TAG_2"); 
        ts2.setIndicator("Menu");
        ts2.setContent(R.id.routeview_help_tab_menu);
        helpTab.addTab(ts2);
        
        mHelpD = new Dialog(this);
        Button b = (Button)v.findViewById(R.id.btnExit);
        b.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				mHelpD.dismiss();
			}
        });
    	mHelpD.setTitle("Route Help");
    	mHelpD.setContentView(v);
    	mHelpD.setCanceledOnTouchOutside(true);
    }
	
    /**
     * Displays messages as toasts.
     * 
     * @param msg	the message to be displayed
     */
	private void displayMessage(String msg) {
		Toast t = Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT);
		t.setGravity(Gravity.TOP, 0, 70);
		if(msg.length() > 20) {
			t.setDuration(Toast.LENGTH_LONG);
		} else {
			t.setDuration(Toast.LENGTH_SHORT);
		}
		t.show();
	}

	/**
	 * Prevents the progress display from being displayed when the thread is used.
	 */
	public static void suppressDialog() {
    	progressOn = false;
    }
    
	/**
	 * Used to see if the thread in fillData is done
	 * 
	 * @return	true if the thread has completed
	 */
    public static boolean isThreadDone() {
    	return threadDone;
    }
}
